﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using UnityEngine;
using System.Collections;

public class FM_ScorePlayer
{
    public const string TAG = Constants.TAG;
    private static FM_ScorePlayer mInstance = null;
    private FM_Audio_Song song;
    private FM_SoundPool soundPlayer;
    internal int SoundsLoaded;
    private readonly CountdownEvent SoundsLoadedCDL = new CountdownEvent(1);
    public CountdownEvent SongLoadedCDL = new CountdownEvent(1);
    private FM_TimeSignatureValue tempTimeSig_n;
    private FM_TimeSignatureValue tempTimeSig_d;
    private FM_ScoreBase score;
    private bool showProgress = false;

    private FM_ScorePlayer() { }

    public static FM_ScorePlayer GetInstance()
    {
        if (mInstance == null)
        {
            lock (typeof(FM_ScorePlayer))
            {
                if (mInstance == null)
                {
                    mInstance = new FM_ScorePlayer();
                    mInstance.score = null;
                    mInstance.showProgress = false;
                    mInstance.SoundsLoaded = 0;
                    mInstance.tempTimeSig_n = FM_TimeSignatureValue._4;
                    mInstance.tempTimeSig_d = FM_TimeSignatureValue._4;
                    mInstance.soundPlayer = null;

                    mInstance.soundPlayer = new FM_SoundPool();
                    mInstance.SetTimeSignature(mInstance.tempTimeSig_n, mInstance.tempTimeSig_d);
                    
                    //new Thread(() =>
                    //{
                    //    mInstance.soundPlayer = new FM_SoundPool();
                    //    mInstance.SetTimeSignature(mInstance.tempTimeSig_n, mInstance.tempTimeSig_d);
                    //}).Start();
                }
            }
        }
        return mInstance;
    }

    public int AssetsLoaded()
    {
        return SoundsLoaded;
    }

    public bool IsPlaying()
    {
        return FM_SoundPool.playing;
    }

    public void SetTimeSignature(FM_TimeSignatureValue n, FM_TimeSignatureValue d)
    {
        tempTimeSig_n = n;
        tempTimeSig_d = d;
        if (soundPlayer != null)
        {
            FM_SoundPool.TIME_SIGNATURE_N = n;
            FM_SoundPool.TIME_SIGNATURE_D = d;
        }
    }

    public long GetQuarterDuration(int tempo)
    {
        return FM_SoundPool.GetDurationInMs(FM_DurationValue.NOTE_QUARTER, 0, tempo, 0);
    }

    public void LoadFromScore(FM_Score obj, int tempo)
    {
        LoadFromScore(obj.ScoreBase, tempo);
    }

    public void LoadFromScore(FM_ScoreBase obj, int tempo)
    {
        song = null;
        score = obj;
        if (FM_SoundPool.playing) StopPlaying();
        SongLoadedCDL = new CountdownEvent(1);
        //Thread loadingThread = new Thread(() =>
        //{
            try
            {
                //SoundsLoadedCDL.Wait();
                soundPlayer.ClearAudioTracks();
                SetTimeSignature(obj.GetTimeSignature_n(), obj.GetTimeSignature_d());
                FM_Helper helper = new FM_Helper();
                song = helper.GenerateSongFromScore(obj, tempo);
                if (showProgress && obj.score != null) obj.score.ProgressReset();
                //SongLoadedCDL.Signal();
            }
            catch (Exception ignored)
            {
                Debug.LogError(ignored.Message + "\n" +ignored.StackTrace);
            }
        //});
        //loadingThread.Start();
    }

    public void Play(ScoreCanvas scoreCanvas, bool mute, Action onPlayEnd)
    {
        if (FM_SoundPool.playing) return;
        FM_SoundPool.playing = true;
        //new Thread(() =>
        {
            //try
            //{
            //    SongLoadedCDL.Wait();
            //}
            //catch (Exception ignored)
            //{
            //    Debug.LogError(ignored.Message);
            //}
            Play(scoreCanvas, song, 1, song.Measures.Count, 0, mute, onPlayEnd);
        }
        //).Start();
    }

    public void Play(ScoreCanvas scoreCanvas, int measure_start, int measure_end, bool mute, Action onPlayEnd)
    {
        Play(scoreCanvas, measure_start, measure_end, 0, mute, onPlayEnd);
    }


    private Action onPlayEnd;

    public void Play(ScoreCanvas scoreCanvas, int measure_start, int measure_end, int notes, bool mute, Action onPlayEnd)
    {
        if (FM_SoundPool.playing) return;
        FM_SoundPool.playing = true;
        //new Thread(() =>
        {
            //try
            //{
            //    SongLoadedCDL.Wait();
            //}
            //catch (Exception ignored)
            //{
            //    Debug.LogError(ignored.Message);
            //}
            Play(scoreCanvas, song, measure_start, measure_end, notes, mute, onPlayEnd);
        }
        //).Start();
    }

    private void LogKeyPlay()
    {
        int i = score.score.progressBar;
        if (i < 0 || i >= score.StaveNotes.Count)
            return;

        FM_BaseNote curNote = score.StaveNotes[i];
        if (curNote is FM_Chord)
        {
            FM_Chord chord = (FM_Chord)curNote;
            foreach (FM_BaseNote curFMNote in chord.Notes)
            {
                Console.WriteLine("playKey: " + "  note:" + curFMNote.note + "  oct" + curFMNote.octave + " acc" + curFMNote.accidental + "  progressBar:" + i);
            }
        }
    }

    private void Play(ScoreCanvas canvas, FM_Audio_Song song, int measure_start, int measure_end, int notes, bool recordMode, Action onPlayEnd)
    {
        canvas.StartCoroutine(IEPlay(canvas, song, measure_start, measure_end, notes, recordMode, onPlayEnd));
    }
      
    IEnumerator IEPlay(ScoreCanvas canvas, FM_Audio_Song song, int measure_start, int measure_end, int notes, bool recordMode, Action onPlayEnd)
    {
        if (measure_end > song.Measures.Count) measure_end = song.Measures.Count;
        if (measure_end == song.Measures.Count) notes = 0;
        List<FM_Audio_Note> listNotes = new List<FM_Audio_Note>();
        for (int i = measure_start - 1; i < measure_end; i++)
        {
            listNotes.AddRange(song.Measures[i].notes);
        }
        if (notes != 0)
        {
            int cnt = notes;
            if (cnt > song.Measures[measure_end].notes.Count)
                cnt = song.Measures[measure_end].notes.Count;
            for (int j = 0; j < cnt; j++)
                listNotes.Add(song.Measures[measure_end].notes[j]);
        }

        //play silent key
        soundPlayer.PlaySilentKey(canvas, 48);
        //FM_SoundPool.CustomDelay(50, false);
        yield return new WaitForSeconds(0.05f);
        soundPlayer.StopKey(48);

        //start playing
        bool inLegato = false;
        if (showProgress && score.score != null) score.score.ProgressSetStart(measure_start);
        long lastDuration = 0;
        for (int i = 0; i < listNotes.Count; i++)
        {
            FM_Audio_Note n = listNotes[i];
            bool isEnd = false;
            if (i == listNotes.Count - 1) isEnd = true;
            if (!FM_SoundPool.playing) break;
            score.score.UpdateCursorShow(canvas);
            if (showProgress && score.score != null) score.score.ProgressAdvance(recordMode);

            if (inLegato || (!isEnd && n.Legato))
            {
                if (n.AudioIntInLegato > 0)
                {
                    LogKeyPlay();
                    soundPlayer.PlayKey(canvas, n.AudioIntInLegato, n.NextPause, recordMode);
                }
                if (n.AudioIntInLegato == 0)
                {
                    //yield return new WaitForSeconds(n.PlayDurationInTie / 1000f);
                    n.AudioTrackInLegato.Play(canvas, n.PlayDurationInTie, n.NextPause, recordMode);
                }
                lastDuration = n.PlayDurationInTie - n.PauseDuration;
            }
            else
            {
                if (n.AudioIntOutsideLegato > 0)
                {
                    LogKeyPlay();
                    soundPlayer.PlayKey(canvas, n.AudioIntOutsideLegato, n.NextPause, recordMode);
                }
                if (n.AudioIntOutsideLegato == 0)
                {
                    //yield return new WaitForSeconds(n.PlayDurationOutsideTie / 1000f);
                    n.AudioTrackOutsideLegato.Play(canvas, n.PlayDurationOutsideTie, n.NextPause, recordMode);
                }
                lastDuration = n.PlayDurationOutsideTie - n.PauseDuration;
            }
            //FM_SoundPool.CustomDelay(n.PauseDuration, false);
            yield return new WaitForSeconds(n.PauseDuration / 1000f);
            if (inLegato || (!isEnd && n.Legato))
            {
                if (n.AudioIntInLegato > 0) soundPlayer.StopKey(n.AudioIntInLegato);
            }
            else
            {
                if (n.AudioIntOutsideLegato > 0) soundPlayer.StopKey(n.AudioIntOutsideLegato);
            }
            inLegato = n.Legato;
            score.score.UpdateCursorShow(canvas);
        }
        //FM_SoundPool.CustomDelay(lastDuration, false);
        yield return new WaitForSeconds(lastDuration / 1000f);
        soundPlayer.StopAllSound();
        FM_SoundPool.playing = false;

        //FM_SoundPool.CustomDelay(1000, false);
        yield return new WaitForSeconds(1f);
        if (score.score != null)
        {
            score.score.OnPlayEnd();
        }
        score.score.UpdateCursorShow(canvas);

        onPlayEnd?.Invoke();
    }

    public void StopPlaying()
    {
        soundPlayer.StopAllSound();
    }

    //Below: functions for a Piano Keyboard

    public bool IsKeyNotPlaying(int key)
    {
        try
        {
            if (key < 1 || key > 89) return false;
            return soundPlayer.IsKeyNotPlaying(key);
        }
        catch (Exception ignored)
        {
            return false;
        }
    }

    //public void PlayKey(int key, bool nextPause)
    //{
    //    if (key < 1 || key > 89) return;
    //    soundPlayer.PlayKey(key, nextPause);
    //}

    public void StopKey(int key)
    {
        if (key < 1 || key > 89) return;
        try
        {
            soundPlayer.StopKey(key);
        }
        catch (Exception ignored)
        {
        }
    }

    public bool IsFirstMeasureComplete()
    {
        if (song.Measures.Count < 2) return true;
        int r_duration = 0;
        int f_duration = 0;
        for (int i = 0; i < song.Measures[1].notes.Count; i++) r_duration = r_duration + (int)song.Measures[1].notes[i].PauseDuration;
        for (int i = 0; i < song.Measures[0].notes.Count; i++) f_duration = f_duration + (int)song.Measures[0].notes[i].PauseDuration;
        return Math.Abs(r_duration - f_duration) < 5;
    }

    public FM_Audio_Song GetSongObject()
    {
        return song;
    }

    public bool IsShowProgress()
    {
        return showProgress;
    }

    public void SetShowProgress(bool showProgress)
    {
        this.showProgress = showProgress;
    }

    //public void CustomDelay(int durationMs)
    //{
    //    FM_SoundPool.CustomDelay(durationMs, false);
    //}

    public void StartPlaying()
    {
        FM_SoundPool.playing = true;
    }
}
